Skip to main content

winsafe\kernel\utilities/
w_string.rs

1use std::cmp::Ordering;
2
3use crate::co;
4use crate::decl::*;
5use crate::kernel::ffi;
6
7/// Stores a `[u16]` buffer for a null-terminated
8/// [Unicode UTF-16](https://learn.microsoft.com/en-us/windows/win32/intl/unicode-in-the-windows-api)
9/// wide string natively used by Windows.
10///
11/// Uses
12/// [Short String Optimization](https://joellaity.com/2020/01/31/string.html)
13/// technique for faster performance.
14///
15/// This is struct is mostly used internally by the library, as a bridge between
16/// Windows and Rust strings.
17#[derive(Default, Clone)]
18pub struct WString {
19	buf: Buffer,
20}
21
22impl std::fmt::Display for WString {
23	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24		let txt = match self.buf.to_string_checked() {
25			Ok(t) => t,
26			Err(e) => format!("PARSING ERROR: {}", e.to_string()),
27		};
28		std::fmt::Display::fmt(&txt, f)
29	}
30}
31impl std::fmt::Debug for WString {
32	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33		std::fmt::Debug::fmt(&self.buf, f)
34	}
35}
36
37impl std::cmp::PartialEq for WString {
38	fn eq(&self, other: &Self) -> bool {
39		self.cmp(other) == Ordering::Equal
40	}
41}
42impl std::cmp::Eq for WString {}
43
44impl std::cmp::PartialOrd for WString {
45	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
46		let ord = unsafe { ffi::lstrcmpW(self.as_ptr(), other.as_ptr()) };
47		Some(if ord < 0 {
48			Ordering::Less
49		} else if ord > 0 {
50			Ordering::Greater
51		} else {
52			Ordering::Equal
53		})
54	}
55}
56impl std::cmp::Ord for WString {
57	fn cmp(&self, other: &Self) -> Ordering {
58		self.partial_cmp(other).unwrap()
59	}
60}
61
62impl WString {
63	/// Stack size for internal
64	/// [Short String Optimization](https://joellaity.com/2020/01/31/string.html).
65	pub const SSO_LEN: usize = Buffer::SSO_LEN;
66
67	/// Stores an UTF-16 null-terminated string from an optional [`&str`](str).
68	///
69	/// If `s` is `None` or the string is empty, no allocation is made.
70	#[must_use]
71	pub fn from_opt_str(s: Option<impl AsRef<str>>) -> Self {
72		Self { buf: Buffer::from_opt_str(s) }
73	}
74
75	/// Stores an UTF-16 null-terminated string from a [`&str`](str).
76	///
77	/// If the string is empty, no allocation is made.
78	#[must_use]
79	pub fn from_str(s: impl AsRef<str>) -> Self {
80		Self { buf: Buffer::from_str(s, ForceHeap::No) }
81	}
82
83	/// Stores an UTF-16 null-terminated string from a [`&str`](str), bypassing
84	/// [Short String Optimization](https://joellaity.com/2020/01/31/string.html)
85	/// – that is, forcing the internal allocation on the heap. This should be
86	/// rarely needed.
87	///
88	/// If the string is empty, no allocation is made.
89	#[must_use]
90	pub fn from_str_force_heap(s: impl AsRef<str>) -> Self {
91		Self { buf: Buffer::from_str(s, ForceHeap::Yes) }
92	}
93
94	/// Stores a series of UTF-16 null-terminated strings. The buffer will end
95	/// with two terminating nulls – that means further retrieval operations
96	/// will "see" only the first string.
97	///
98	/// If the slice is empty, no allocation is made.
99	///
100	/// This method can be used as an escape hatch to interoperate with other
101	/// libraries.
102	#[must_use]
103	pub fn from_str_vec(v: &[impl AsRef<str>]) -> Self {
104		Self { buf: Buffer::from_str_vec(v) }
105	}
106
107	/// Stores an UTF-16 null-terminated string by copying from a buffer,
108	/// specifying the number of chars to be copied.
109	///
110	/// The `src` buffer doesn't need to be null-terminated.
111	#[must_use]
112	pub fn from_wchars_count(src: *const u16, num_chars: usize) -> Self {
113		Self {
114			buf: Buffer::from_wchars_count(src, num_chars),
115		}
116	}
117
118	/// Stores an UTF-16 null-terminated string by copying from a
119	/// null-terminated buffer. The string length is retrieved with
120	/// [`lstrlen`](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lstrlenw).
121	///
122	/// # Safety
123	///
124	/// Be sure the string is null-terminated, otherwise `lstrlen` will get
125	/// lost, possibly reading an invalid memory location.
126	#[must_use]
127	pub unsafe fn from_wchars_nullt(src: *const u16) -> Self {
128		Self {
129			buf: unsafe { Buffer::from_wchars_nullt(src) },
130		}
131	}
132
133	/// Stores an UTF-16 null-terminated string by copying from a slice.
134	///
135	/// The `src` slice doesn't need to be null-terminated.
136	#[must_use]
137	pub fn from_wchars_slice(src: &[u16]) -> Self {
138		Self { buf: Buffer::from_wchars_slice(src) }
139	}
140
141	/// Constructs a new, empty `WString`. No allocation is made.
142	#[must_use]
143	pub const fn new() -> Self {
144		Self { buf: Buffer::new() }
145	}
146
147	/// Allocates an UTF-16 buffer with an specific length. All elements will be
148	/// set to zero.
149	#[must_use]
150	pub fn new_alloc_buf(sz: usize) -> Self {
151		Self {
152			buf: Buffer::new_alloc_buf(sz, ForceHeap::No),
153		}
154	}
155
156	/// Returns a mutable
157	/// [`LPWSTR`](https://learn.microsoft.com/en-us/windows/win32/learnwin32/working-with-strings)
158	/// pointer to the internal UTF-16 string buffer, to be passed to native
159	/// Win32 functions. This is useful to receive strings.
160	///
161	/// # Panics
162	///
163	/// Panics if the buffer was not allocated.
164	///
165	/// # Safety
166	///
167	/// Be sure to alloc enough room, otherwise a buffer overrun may occur.
168	#[must_use]
169	pub const unsafe fn as_mut_ptr(&mut self) -> *mut u16 {
170		unsafe { self.buf.as_mut_ptr() }
171	}
172
173	/// Returns a mutable slice to the internal UTF-16 string buffer.
174	#[must_use]
175	pub const fn as_mut_slice(&mut self) -> &mut [u16] {
176		self.buf.as_mut_slice()
177	}
178
179	/// Returns a
180	/// [`LPCWSTR`](https://learn.microsoft.com/en-us/windows/win32/learnwin32/working-with-strings)
181	/// pointer to the internal UTF-16 string buffer, to be passed to native
182	/// Win32 functions.
183	///
184	/// If the buffer was not allocated, returns a null pointer.
185	#[must_use]
186	pub const fn as_ptr(&self) -> *const u16 {
187		self.buf.as_ptr()
188	}
189
190	/// Returns a slice to the internal UTF-16 string buffer.
191	#[must_use]
192	pub const fn as_slice(&self) -> &[u16] {
193		self.buf.as_slice()
194	}
195
196	/// Returns the size of the allocated internal buffer, in `u16` wide chars.
197	/// Note that the terminating null, if existing, is also counted.
198	///
199	/// If the buffer was not allocated yet, returns zero.
200	#[must_use]
201	pub const fn buf_len(&self) -> usize {
202		self.buf.buf_len()
203	}
204
205	/// Copies the content into an external buffer. A terminating null will be
206	/// appended.
207	///
208	/// If `dest` is smaller, the string will be truncated.
209	///
210	/// If `dest` has 1 element, it will receive only the terminating null.
211	pub fn copy_to_slice(&self, dest: &mut [u16]) {
212		if !dest.is_empty() {
213			let usable_len = dest.len() - 1; // leave room for terminating null
214			self.as_slice()
215				.iter()
216				.zip(dest[..usable_len].iter_mut())
217				.for_each(|(src, dest)| *dest = *src);
218			dest[usable_len..]
219				.iter_mut()
220				.for_each(|dest| *dest = 0x0000); // fill the rest with zero
221		}
222	}
223
224	/// Fills the entire buffer with zeros.
225	pub fn fill_with_zero(&mut self) {
226		self.as_mut_slice().iter_mut().for_each(|ch| *ch = 0x0000);
227	}
228
229	/// Returns `true` if the internal buffer has been allocated.
230	#[must_use]
231	pub const fn is_allocated(&self) -> bool {
232		self.buf.is_allocated()
233	}
234
235	/// Converts into [`String`](std::string::String) by calling
236	/// [`String::from_utf16`](std::string::String::from_utf16). An uncallocated
237	/// will simply be converted into an empty string.
238	///
239	/// This method is useful if you're parsing raw data which may contain
240	/// invalid characters. If you're dealing with a string known to be valid,
241	/// [`to_string`](std::string::ToString::to_string) is more practical.
242	#[must_use]
243	pub fn to_string_checked(&self) -> Result<String, std::string::FromUtf16Error> {
244		self.buf.to_string_checked()
245	}
246
247	/// Wrapper to
248	/// [`lstrlen`](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lstrlenw).
249	///
250	/// Returns the number of [`u16`] characters stored in the internal buffer,
251	/// not counting the terminating null.
252	#[must_use]
253	pub fn str_len(&self) -> usize {
254		unsafe { ffi::lstrlenW(self.buf.as_ptr()) as _ }
255	}
256
257	/// Converts the string to lower case, in-place. Wrapper to
258	/// [`CharLower`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-charlowerw).
259	pub fn make_lowercase(&mut self) {
260		unsafe { ffi::CharLowerW(self.as_mut_ptr()) };
261	}
262
263	/// Converts the string to upper case, in-place. Wrapper to
264	/// [`CharUpper`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-charupperw).
265	pub fn make_uppercase(&mut self) {
266		unsafe { ffi::CharUpperW(self.as_mut_ptr()) };
267	}
268
269	/// Guesses the encoding with [`Encoding::guess`](crate::Encoding::guess)
270	/// and parses the data as a string.
271	///
272	/// If you're sure the data has UTF-8 encoding, you can also use the
273	/// built-in [`String::from_utf8`](std::string::String::from_utf8).
274	///
275	/// To serialize the string back into UTF-8 bytes, use the built-in
276	/// [`String::into_bytes`](std::string::String::into_bytes).
277	///
278	/// # Examples
279	///
280	/// Usually the fastest way to read the text from a file is by mapping its
281	/// contents in memory with [`FileMapped`](crate::FileMapped), then parsing:
282	///
283	/// ```no_run
284	/// use winsafe::{self as w, prelude::*};
285	///
286	/// let file_in = w::FileMapped::open(
287	///     "C:\\Temp\\foo.txt",
288	///     w::FileAccess::ExistingReadOnly,
289	/// )?;
290	/// let wstr = w::WString::parse(file_in.as_slice())?;
291	/// let str_contents = wstr.to_string();
292	/// # w::SysResult::Ok(())
293	/// ```
294	#[must_use]
295	pub fn parse(data: &[u8]) -> SysResult<Self> {
296		let mut data = data;
297		if data.is_empty() {
298			return Ok(Self::new()); // nothing to parse
299		}
300
301		let (encoding, sz_bom) = Encoding::guess(data);
302		data = &data[sz_bom..]; // skip BOM, if any
303
304		Ok(Self::from_wchars_slice(&match encoding {
305			Encoding::Ansi => Self::parse_ansi(data),
306			Encoding::Win1252 => MultiByteToWideChar(co::CP::WINDOWS_1252, co::MBC::NoValue, data)?,
307			Encoding::Utf8 => MultiByteToWideChar(co::CP::UTF8, co::MBC::NoValue, data)?,
308			Encoding::Utf16be => Self::parse_utf16(data, true),
309			Encoding::Utf16le => Self::parse_utf16(data, false),
310			Encoding::Utf32be
311			| Encoding::Utf32le
312			| Encoding::Scsu
313			| Encoding::Bocu1
314			| Encoding::Unknown => panic!("Encoding {} not implemented.", encoding),
315		}))
316	}
317
318	fn parse_ansi(data: &[u8]) -> Vec<u16> {
319		data.iter()
320			.take_while(|ch| **ch != 0x0000) // ignore terminating null, if any
321			.map(|ch| *ch as u16) // raw u8 to u16 conversion
322			.collect()
323	}
324
325	fn parse_utf16(data: &[u8], is_big_endian: bool) -> Vec<u16> {
326		let data = if data.len() % 2 == 1 {
327			&data[..data.len() - 1] // if odd number of bytes, discard last one
328		} else {
329			data
330		};
331
332		data.chunks(2)
333			.take_while(|ch2| **ch2 != [0x00, 0x00]) // ignore terminating null, if any
334			.map(|ch2| {
335				if is_big_endian {
336					u16::from_be_bytes(ch2.try_into().unwrap())
337				} else {
338					u16::from_le_bytes(ch2.try_into().unwrap())
339				}
340			})
341			.collect()
342	}
343}
344
345#[derive(PartialEq, Eq)]
346enum ForceHeap {
347	Yes,
348	No,
349}
350
351enum Buffer {
352	Stack([u16; Self::SSO_LEN]),
353	Heap(Vec<u16>),
354	Unallocated,
355}
356
357impl Default for Buffer {
358	fn default() -> Self {
359		Self::Unallocated
360	}
361}
362
363impl Clone for Buffer {
364	fn clone(&self) -> Self {
365		match self {
366			Self::Unallocated => Self::Unallocated,
367			_ => {
368				let mut new_self = Self::new_alloc_buf(self.buf_len(), ForceHeap::No);
369				self.as_slice()
370					.iter()
371					.zip(new_self.as_mut_slice())
372					.for_each(|(src, dest)| *dest = *src);
373				new_self
374			},
375		}
376	}
377}
378
379impl std::fmt::Debug for Buffer {
380	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
381		let txt = match self.to_string_checked() {
382			Ok(t) => t,
383			Err(e) => format!("PARSING ERROR: {}", e.to_string()),
384		};
385		write!(
386			f,
387			"{}",
388			match self {
389				Self::Stack(_) => format!("STACK({}) \"{}\"", self.buf_len(), txt),
390				Self::Heap(_) => format!("HEAP({}) \"{}\"", self.buf_len(), txt),
391				Self::Unallocated => "UNALLOCATED \"\"".to_owned(),
392			}
393		)
394	}
395}
396
397impl Buffer {
398	pub const SSO_LEN: usize = 20;
399
400	#[must_use]
401	fn from_opt_str(s: Option<impl AsRef<str>>) -> Self {
402		match s {
403			Some(s) => Self::from_str(s, ForceHeap::No),
404			None => Self::Unallocated,
405		}
406	}
407
408	#[must_use]
409	fn from_str(s: impl AsRef<str>, force_heap: ForceHeap) -> Self {
410		let s_len = s.as_ref().encode_utf16().count();
411		if s_len == 0 {
412			Self::Unallocated
413		} else {
414			let num_chars = s_len + 1; // room for terminating null
415			let mut new_self = Self::new_alloc_buf(num_chars, force_heap);
416			s.as_ref()
417				.encode_utf16()
418				.zip(new_self.as_mut_slice())
419				.for_each(|(src, dest)| *dest = src);
420			new_self
421		}
422	}
423
424	#[must_use]
425	fn from_str_vec(v: &[impl AsRef<str>]) -> Self {
426		if v.is_empty() {
427			return Self::Unallocated; // no elements yield an empty buffer
428		}
429
430		let tot_chars = v.iter() // number of chars of all strings, including terminating nulls
431			.fold(0, |tot, s| tot + s.as_ref().chars().count() + 1) // include terminating null
432			+ 1; // double terminating null
433		let mut new_self = Self::new_alloc_buf(tot_chars, ForceHeap::No);
434		v.iter()
435			.map(|s| {
436				s.as_ref().encode_utf16().chain(std::iter::once(0x0000)) // append terminating null on each string
437			})
438			.flatten()
439			.zip(new_self.as_mut_slice())
440			.for_each(|(src, dest)| *dest = src);
441		new_self
442	}
443
444	#[must_use]
445	fn from_wchars_count(src: *const u16, num_chars: usize) -> Self {
446		if src.is_null() || num_chars == 0 {
447			Self::Unallocated
448		} else {
449			Self::from_wchars_slice(unsafe { std::slice::from_raw_parts(src, num_chars) })
450		}
451	}
452
453	#[must_use]
454	unsafe fn from_wchars_nullt(src: *const u16) -> Self {
455		Self::from_wchars_count(src, unsafe { ffi::lstrlenW(src) as _ })
456	}
457
458	#[must_use]
459	fn from_wchars_slice(src: &[u16]) -> Self {
460		if src.is_empty() {
461			Self::Unallocated
462		} else {
463			let num_chars = src
464				.iter()
465				.take_while(|ch| **ch != 0x0000) // skip terminating null, if any
466				.count() + 1; // room for terminating null
467			let mut new_self = Self::new_alloc_buf(num_chars, ForceHeap::No);
468			src.iter()
469				.take_while(|ch| **ch != 0x0000) // skip terminating null, if any
470				.zip(new_self.as_mut_slice())
471				.for_each(|(src, dest)| *dest = *src);
472			new_self
473		}
474	}
475
476	#[must_use]
477	const fn new() -> Self {
478		Self::Unallocated
479	}
480
481	#[must_use]
482	fn new_alloc_buf(num_chars: usize, force_heap: ForceHeap) -> Self {
483		if num_chars == 0 {
484			Self::Unallocated
485		} else if force_heap == ForceHeap::Yes || num_chars > Self::SSO_LEN {
486			Self::Heap(vec![0x0000; num_chars])
487		} else {
488			Self::Stack([0x0000; Self::SSO_LEN])
489		}
490	}
491
492	#[must_use]
493	const unsafe fn as_mut_ptr(&mut self) -> *mut u16 {
494		match self {
495			Self::Stack(arr) => arr.as_mut_ptr(),
496			Self::Heap(vec) => {
497				if vec.is_empty() {
498					std::ptr::null_mut() // because empty Vec returns garbage as ptr
499				} else {
500					vec.as_mut_ptr()
501				}
502			},
503			Self::Unallocated => panic!("Trying to use an unallocated WString buffer."),
504		}
505	}
506
507	#[must_use]
508	const fn as_mut_slice(&mut self) -> &mut [u16] {
509		match self {
510			Self::Stack(arr) => arr,
511			Self::Heap(vec) => vec.as_mut_slice(),
512			Self::Unallocated => &mut [],
513		}
514	}
515
516	#[must_use]
517	const fn as_ptr(&self) -> *const u16 {
518		match self {
519			Self::Stack(arr) => arr.as_ptr(),
520			Self::Heap(vec) => {
521				if vec.is_empty() {
522					std::ptr::null() // because empty Vec returns garbage as ptr
523				} else {
524					vec.as_ptr()
525				}
526			},
527			Self::Unallocated => std::ptr::null(),
528		}
529	}
530
531	#[must_use]
532	const fn as_slice(&self) -> &[u16] {
533		match self {
534			Self::Stack(arr) => arr,
535			Self::Heap(vec) => vec.as_slice(),
536			Self::Unallocated => &[],
537		}
538	}
539
540	#[must_use]
541	const fn buf_len(&self) -> usize {
542		match self {
543			Self::Stack(arr) => arr.len(),
544			Self::Heap(vec) => vec.len(),
545			Self::Unallocated => 0,
546		}
547	}
548
549	#[must_use]
550	const fn is_allocated(&self) -> bool {
551		match self {
552			Self::Unallocated => false,
553			_ => true,
554		}
555	}
556
557	#[must_use]
558	fn to_string_checked(&self) -> Result<String, std::string::FromUtf16Error> {
559		match self {
560			Self::Unallocated => Ok(String::new()),
561			_ => String::from_utf16(
562				&self
563					.as_slice()
564					.into_iter()
565					.take_while(|ch| **ch != 0x0000) // remove all trailing zeros
566					.map(|ch| *ch)
567					.collect::<Vec<_>>(),
568			),
569		}
570	}
571}